package com.lp.search.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lp.feign.ArticleClients;
import com.lp.model.config.AppResult;
import com.lp.model.config.CodeMsg;
import com.lp.model.constants.ESConstants;
import com.lp.model.dto.search.SearchPageDto;
import com.lp.model.dto.search.SearchPrefix;
import com.lp.model.entity.article.ArticleStat;
import com.lp.model.entity.article.vo.ArticleESVO;
import com.lp.model.entity.article.vo.ArticleVO;
import com.lp.model.entity.article.vo.PageVO;
import com.lp.model.exception.ApplicationException;
import com.lp.model.utils.PageUtils;
import com.lp.search.service.IArticleSearchService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Service
public class ArticleSearchService implements IArticleSearchService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ArticleClients articleClients;

    @Autowired
    private ObjectMapper objectMapper;

    @SneakyThrows
    @Override
    public PageVO<ArticleVO> search(SearchPageDto pageDto) {
        //1.准备请求
        SearchRequest request = new SearchRequest(ESConstants.INDEX_ARTICLE);
        //2.准备参数(Ps: 没有内容就查询所有)
        if(!StringUtils.hasLength(pageDto.getContent())) {
            request.source().query(QueryBuilders.matchAllQuery());
        } else {
            request.source().query(QueryBuilders.matchQuery(ESConstants.ARTICLE_ALL, pageDto.getContent()));
        }
        //3.分页处理
        int limit = pageDto.getPageSize();
        int offset = (pageDto.getCurPage() - 1) * pageDto.getPageSize();
        request.source().size(limit).from(offset);
        //4.发送请求，并接收响应
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //6.解析响应
        PageVO<ArticleVO> pageVO = handlerResponse(response);
        return pageVO;
    }

    @SneakyThrows
    @Override
    public void insertByArticleId(Long articleId) {
        //1.获取文章 esvo
        ArticleESVO articleESVO = getESVOByArticleId(articleId);
        //2.转化成 json
        String articleJson = objectMapper.writeValueAsString(articleESVO);
        //3.创建 articleESVO 索引库
        IndexRequest request = new IndexRequest(ESConstants.INDEX_ARTICLE).id(articleESVO.getId().toString());
        request.source(articleJson, XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
    }

    @SneakyThrows
    @Override
    public void delByArticleId(Long articleId) {
        //1.创建删除请求
        DeleteRequest request = new DeleteRequest(ESConstants.INDEX_ARTICLE).id(articleId.toString());
        //2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

    @SneakyThrows
    @Override
    public void updateByArticleId(Long articleId) {
        //1.创建修改请求
        UpdateRequest request = new UpdateRequest(ESConstants.INDEX_ARTICLE, articleId.toString());
        //2.填写修改参数
        //1) 远程调用获取修改后的文章信息
        ArticleESVO articleESVO = getESVOByArticleId(articleId);
        String result = objectMapper.writeValueAsString(articleESVO);
        //2) 修改文档
        request.doc(result, XContentType.JSON);
        //2.发送修改请求
        client.update(request, RequestOptions.DEFAULT);
    }

    @SneakyThrows
    @Override
    public List<String> suggestion(SearchPrefix prefix) {
        //1.创建请求
        SearchRequest request = new SearchRequest(ESConstants.INDEX_ARTICLE);
        //2.准备参数
        request.source().suggest(new SuggestBuilder().addSuggestion(
                ESConstants.ARTICLE_SUG,
                SuggestBuilders
                        .completionSuggestion(ESConstants.SUGGESTION)
                        .prefix(prefix.getPrefix())
                        .skipDuplicates(true)
                        .size(ESConstants.ARTICLE_SUG_SIZE)
        ));
        //3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //4.解析响应获取自动补全列表
        List<String> sugList = getSugList(response);
        return sugList;
    }

    /**
     * 解析响应获取自动补全列表
     * @param response 搜索响应
     */
    private List<String> getSugList(SearchResponse response) {
        Suggest suggest = response.getSuggest();
        List<String> suggestionList = new ArrayList<>();
        if(suggest != null) {
            CompletionSuggestion suggestion = suggest.getSuggestion(ESConstants.ARTICLE_SUG);
            for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
                String text = option.getText().toString();
                suggestionList.add(text);
            }
        }
        return suggestionList;
    }



    @SneakyThrows
    private PageVO<ArticleVO> handlerResponse(SearchResponse response) {
        //1.解析结果
        SearchHits hits = response.getHits();
        long total = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        List<ArticleVO> articleVOList = new ArrayList<>();
        for(SearchHit searchHit : hits1) {
            //1) 获取source
            String json = searchHit.getSourceAsString();
            //2) 解析出 esvo
            ArticleESVO articleESVO = objectMapper.readValue(json, ArticleESVO.class);
            //3) 远程调用获取文章统计信息并解析
            String appResultJson = articleClients.getArticleStat(articleESVO.getId());
            if(!StringUtils.hasLength(appResultJson)) {
                log.info(CodeMsg.FAIL_ERROR_RPC_ARTICLE.toString());
                throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_RPC_ARTICLE));
            }
            AppResult result = objectMapper.readValue(appResultJson, AppResult.class);
            ArticleStat articleStat = objectMapper.convertValue(result.getData(),
                    new TypeReference<ArticleStat>() {});
            //4) 整合到 vo 中
            ArticleVO articleVO = getArticleVO(articleESVO, articleStat);
            articleVOList.add(articleVO);
        }
        return PageUtils.getPageVO(articleVOList, total);
    }

    /**
     * 整合 VO
     */
    private ArticleVO getArticleVO(ArticleESVO articleESVO, ArticleStat articleStat) {
        ArticleVO articleVO = new ArticleVO();
        BeanUtils.copyProperties(articleESVO, articleVO);
        articleVO.setVisits(articleStat.getVisits());
        articleVO.setLikeCount(articleStat.getLikeCount());
        articleVO.setCollectCount(articleStat.getCollectCount());
        articleVO.setCommentCount(articleStat.getCommentCount());
        return articleVO;
    }


    /**
     * 远程调用获取文章 esvo
     * @param articleId
     * @return
     */
    @SneakyThrows
    private ArticleESVO getESVOByArticleId(Long articleId) {
        String info = articleClients.ESVO(articleId);
        if(info == null) {
            log.info(CodeMsg.FAIL_ERROR_RPC_ARTICLE.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_RPC_ARTICLE));
        }
        AppResult result = objectMapper.readValue(info, AppResult.class);
        ArticleESVO articleESVO = objectMapper.convertValue(result.getData(),
                new TypeReference<ArticleESVO>() {});
        return articleESVO;
    }


}
